package main

import (
	"unsafe"
)

// 在Go中, 自定义一个新类型一般有两种方法。
// 第一种是类型定义(Type Definition), 也是最常用的类型定义方法
// type T s // 定义一个新类型T, 在这里, s可以是一个已定义的类型，包括Go原生类型，或者是其他已定义的自定义类型

func main() {
	// 如果一个新类型是基于某个Go原生类型定义的，那么这个Go原生类型为新类型的底层类型(Underlying Type)
	type T1 int // 这里 类型int就是类型T1的底层类型
	// 底层类型被用来判断两个类型本质上是否相同(Identical).
	// 本质上相同的两个类型。它们的变量可以通过显示转型进行相互赋值，相反，如果本质上是不同的两个类型，它们的变量间连显式转型都不可能，更不要说相互赋值了
	type T2 T1
	type T3 string
	var n1 T1
	var n2 T2 = 5
	n1 = T1(n2) // ok

	var s T3 = "hello"
	// n1 = T1(s) // 错误: cannot convert s (type T3) to type T1
	println(n1, s)

	// 除了基于已有类型定义新类型之外，还可以基于类型字面值来定义新类型
	type M map[int]string
	type S []string
	// 和变量声明支持使用var块的方式类似，类型定义也支持通过type块的方式进行
	// type (
	// 	T1 int
	// 	T2 T1
	// 	T3 string
	// )
	// 第二种自定义新类型的方式是使用类型别名(Type Alias)
	type T = S // type alias

	type Book1 struct {
		Title   string         // 书名
		Pages   int            // 书的页数
		Indexes map[string]int // 书的索引
	}

	// 定义一个空结构体
	type Empty struct{} // Empty是一个不包含任何自断的空结构体类型
	var ep Empty
	println(unsafe.Sizeof(ep)) // 0
	// 空结构体类型变量的内存占用为0.基于空结构体类型内存零开销这样的特性，在日常Go开发中经常使用空结构体类型元素，作为一种"事件"信息进行Goroutine之间的通信
	// var c = make(chan Empty) // 声明一个元素类型为Empty的channel
	// c <- Empty{}             // 向channel 写入一个"事件"
	// 这种以空结构体为元素类建立的channel, 是目前能实现的、内存占用最小的Goroutinue间通信方式

	// 使用其他结构体作为自定义结构体中字段的类型
	type Person struct {
		Name  string
		Phone string
		Addr  string
	}

	type Book struct {
		Title   string         // 书名
		Author  Person         // 作者
		Pages   int            // 书的页数
		Indexes map[string]int // 书的索引
	}

	// 使用复合字面值初始化
	book := Book{
		Title: "测试",
		Author: Person{
			Name:  "习",
			Phone: "11111",
			Addr:  "中南海",
		},
		Pages: 10,
	}
	println(book.Author.Phone)

	println(unsafe.Sizeof(book))         // 结构体类型变量占用内存大小
	println(unsafe.Offsetof(book.Pages)) // 字段Pages在内存中相对于变量book起始地址的偏移量
}
